﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Globalization;
using System.IO;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Modeling;
using EnvDTE;

namespace DSLFactory.VSCTDesigner
{
    internal class VSHelper
    {
        private IVsSolution vsSolution;

        public VSHelper(Store store)
        {
             vsSolution = (IVsSolution)store.GetService(typeof(IVsSolution));
        }

        public void IncrementMenuResource()
        {
            try
            {
                CodeAttribute provideMenuResourceAttribute = null;

                HierarchyNode node = CurrentSolution.RecursiveFind(p=> {
                    if( p.Name != null && p.Name.Equals("Package.cs", StringComparison.CurrentCultureIgnoreCase) )
                    {
                        ProjectItem pi = p.ExtObject as ProjectItem;
                        foreach( CodeElement cel in pi.FileCodeModel.CodeElements )
                        {
                            if( cel.Kind == vsCMElement.vsCMElementNamespace )
                            {
                                CodeNamespace cn = cel as CodeNamespace;
                                foreach( CodeElement cel2 in cn.Members )
                                {
                                    if( cel2.Kind == vsCMElement.vsCMElementClass )
                                    {
                                        CodeClass cc = cel2 as CodeClass;
                                        foreach( CodeAttribute att in cc.Attributes )
                                        {
                                            if( att.Name == "VSShell::ProvideMenuResource" )
                                            {
                                                provideMenuResourceAttribute = att;
                                                return true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return false;
                });

                if( provideMenuResourceAttribute != null )
                {
                    string[] values = provideMenuResourceAttribute.Value.Split(',');
                    int v = Int32.Parse(values[1].Trim());
                    v++;
                    values[1] = v.ToString();
                    provideMenuResourceAttribute.Value = String.Join(",", values);
                }
            }
            catch { 
            // Prevent user to increment manually
            }
        }

        public bool CanAssignGeneratorTool(string fileName)
        {
            if( String.IsNullOrEmpty(fileName) )
                throw new ArgumentNullException(fileName);
            return GetNode(fileName) != null;
        }

        public void AssignGeneratorTool(string fileName, string generatorName)
        {
            if( String.IsNullOrEmpty(fileName) )
                throw new ArgumentNullException(fileName);
            if( String.IsNullOrEmpty(generatorName) )
                throw new ArgumentNullException(generatorName);
            HierarchyNode node = GetNode(fileName);
            if( node != null )
            {
                EnvDTE.ProjectItem pi = node.GetProperty<EnvDTE.ProjectItem>(__VSHPROPID.VSHPROPID_ExtObject);
                if( pi != null )
                {
                    if( (string)pi.Properties.Item("CustomTool").Value != generatorName)
                        pi.Properties.Item("CustomTool").Value = generatorName;
                }
            }
        }

        HierarchyNode GetNode(string fileName)
        {
            if( String.IsNullOrEmpty(fileName) )
                throw new ArgumentNullException(fileName);

            fileName = Path.GetFullPath(fileName);
            return CurrentSolution.RecursiveFind(node => node.Path != null && node.Path.Equals(fileName, StringComparison.CurrentCultureIgnoreCase));
        }

        HierarchyNode CurrentSolution
        {
            get
            {
                Debug.Assert(vsSolution != null);
                return new HierarchyNode(vsSolution);
            }
        }
    }

    /// <summary>
    /// HierarchyNode excerpt from Web Service Software Factory source code
    /// </summary>
    internal class HierarchyNode
    {
        private IVsHierarchy hierarchy;
        private IVsSolution solution;
        private uint itemId;

        /// <summary>
        /// Constructs a HierarchyNode at the solution root
        /// </summary>
        /// <param name="vsSolution"></param>
        // FXCOP: false positive.
        [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
        public HierarchyNode(IVsSolution vsSolution)
            : this(vsSolution, Guid.Empty)
        {
        }

        /// <summary>
        /// Constructs a HierarchyNode given the projectGuid
        /// </summary>
        /// <param name="vsSolution"></param>
        /// <param name="projectGuid"></param>
        // FXCOP: False positive
        [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
        public HierarchyNode(IVsSolution vsSolution, Guid projectGuid)
        {
            if( vsSolution == null )
                throw new ArgumentNullException("vsSolution");

            IVsHierarchy rootHierarchy = null;
            int hr = vsSolution.GetProjectOfGuid(ref projectGuid, out rootHierarchy);
            if( rootHierarchy == null )
            {
                throw new InvalidOperationException(
                    String.Format(CultureInfo.CurrentCulture, "Project does not exist", projectGuid.ToString("b")),
                    Marshal.GetExceptionForHR(hr));
            }
            Init(vsSolution, rootHierarchy, VSConstants.VSITEMID_ROOT);
        }

        /// <summary>
        /// Constructs a hierarchy node at the root level of hierarchy
        /// </summary>
        /// <param name="vsSolution"></param>
        /// <param name="hierarchy"></param>
        // FXCOP: False positive
        [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
        public HierarchyNode(IVsSolution vsSolution, IVsHierarchy hierarchy)
        {
            if(vsSolution==null)
                throw new ArgumentNullException("vsSolution");

            Init(vsSolution, hierarchy, VSConstants.VSITEMID_ROOT);
        }

        /// <summary>
        /// Builds a child HierarchyNode from the parent node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="childId"></param>
        // FXCOP: False positive
        [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
        public HierarchyNode(HierarchyNode parent, uint childId)
        {
            if( parent == null )
                throw new ArgumentNullException("parent");

            Init(parent.solution, parent.hierarchy, childId);
        }

        /// <summary>
        /// Inits the specified vs solution.
        /// </summary>
        /// <param name="vsSolution">The vs solution.</param>
        /// <param name="vsHierarchy">The vs hierarchy.</param>
        /// <param name="vsItemId">The vs item id.</param>
        private void Init(IVsSolution vsSolution, IVsHierarchy vsHierarchy, uint vsItemId)
        {
            this.solution = vsSolution;
            int hr = VSConstants.E_FAIL;
            if( vsHierarchy == null )
            {
                Guid emptyGuid = Guid.Empty;
                hr = this.solution.GetProjectOfGuid(ref emptyGuid, out this.hierarchy);
                Marshal.ThrowExceptionForHR(hr);
            }
            else
            {
                this.hierarchy = vsHierarchy;
            }
            this.itemId = vsItemId;

            IntPtr nestedHierarchyObj;
            uint nestedItemId;
            Guid hierGuid = typeof(IVsHierarchy).GUID;

            // Check first if this node has a nested hierarchy. If so, then there really are two 
            // identities for this node: 1. hierarchy/itemid 2. nestedHierarchy/nestedItemId.
            // We will convert this node using the inner nestedHierarchy/nestedItemId identity.
            hr = this.hierarchy.GetNestedHierarchy(this.itemId, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
            if( VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj )
            {
                IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
                Marshal.Release(nestedHierarchyObj); // we are responsible to release the refcount on the out IntPtr parameter
                if( nestedHierarchy != null )
                {
                    this.hierarchy = nestedHierarchy;
                    this.itemId = nestedItemId;
                }
            }
        }

        /// <summary>
        /// Returns the extensibility object
        /// </summary>
        public object ExtObject
        {
            get { return GetProperty<object>(__VSHPROPID.VSHPROPID_ExtObject); }
        }

        /// <summary>
        /// Name of this node
        /// </summary>
        public string Name
        {
            get { return GetProperty<string>(__VSHPROPID.VSHPROPID_Name); }
        }

        /// <summary>
        /// Returns the full path
        /// </summary>
        /// <returns></returns>
        public string Path
        {
            get
            {
                string path = string.Empty;
                if( hierarchy is IVsProject )
                {
                    int hr = ( (IVsProject)hierarchy ).GetMkDocument(itemId, out path);
                    //Marshal.ThrowExceptionForHR(hr);
                    return path;
                }
                else
                {
                    return string.Empty;
                }
            }
        }

        public HierarchyNode RecursiveFind(Predicate<HierarchyNode> func)
        {
            if( func(this) )
            {
                return this;
            }

            foreach( HierarchyNode child in this.Children )
            {
                HierarchyNode found = child.RecursiveFind(func);
                if( found != null )
                {
                    return found;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the children.
        /// </summary>
        /// <value>The children.</value>
        public IEnumerable<HierarchyNode> Children
        {
            get { return new HierarchyNodeCollection(this); }
        }

        /// <summary>
        /// Returns the item id of the first child
        /// </summary>
        public uint FirstChildId
        {
            get
            {
                //Get the first child node of the current hierarchy being walked
                // NOTE: to work around a bug with the Solution implementation of VSHPROPID_FirstChild,
                // we keep track of the recursion level. If we are asking for the first child under
                // the Solution, we use VSHPROPID_FirstVisibleChild instead of _FirstChild. 
                // In VS 2005 and earlier, the Solution improperly enumerates all nested projects
                // in the Solution (at any depth) as if they are immediate children of the Solution.
                // Its implementation _FirstVisibleChild is correct however, and given that there is
                // not a feature to hide a SolutionFolder or a Project, thus _FirstVisibleChild is 
                // expected to return the identical results as _FirstChild.
                return GetItemId(GetProperty<object>(IsSolution ? __VSHPROPID.VSHPROPID_FirstVisibleChild : __VSHPROPID.VSHPROPID_FirstChild));
            }
        }
        /// <summary>
        /// Gets the next child id from the passed childId
        /// </summary>
        /// <param name="childId"></param>
        /// <returns></returns>
        public uint GetNextChildId(uint childId)
        {
            object nextChild = null;
            // NOTE: to work around a bug with the Solution implementation of VSHPROPID_NextSibling,
            // we keep track of the recursion level. If we are asking for the next sibling under
            // the Solution, we use VSHPROPID_NextVisibleSibling instead of _NextSibling. 
            // In VS 2005 and earlier, the Solution improperly enumerates all nested projects
            // in the Solution (at any depth) as if they are immediate children of the Solution.
            // Its implementation   _NextVisibleSibling is correct however, and given that there is
            // not a feature to hide a SolutionFolder or a Project, thus _NextVisibleSibling is 
            // expected to return the identical results as _NextSibling.
            hierarchy.GetProperty(childId,
                    (int)( IsSolution ? __VSHPROPID.VSHPROPID_NextVisibleSibling : __VSHPROPID.VSHPROPID_NextSibling ),
                    out nextChild);
            return GetItemId(nextChild);
        }

        /// <summary>
        /// Gets the item id.
        /// </summary>
        /// <param name="pvar">The pvar.</param>
        /// <returns></returns>
        private static uint GetItemId(object pvar)
        {
            if( pvar == null )
                return VSConstants.VSITEMID_NIL;
            if( pvar is int )
                return (uint)(int)pvar;
            if( pvar is uint )
                return (uint)pvar;
            if( pvar is short )
                return (uint)(short)pvar;
            if( pvar is ushort )
                return (uint)(ushort)pvar;
            if( pvar is long )
                return (uint)(long)pvar;
            return VSConstants.VSITEMID_NIL;
        }

        /// <summary>
        /// Returns true if the current node is the solution root
        /// </summary>
        public bool IsSolution
        {
            get
            {
                return ( Parent == null );
            }
        }

        /// <summary>
        /// Returns true if this a root node of another node
        /// </summary>
        public bool IsRoot
        {
            get { return ( VSConstants.VSITEMID_ROOT == itemId ); }
        }

        /// <summary>
        /// Gets the property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propId">The prop id.</param>
        /// <param name="itemid">The itemid.</param>
        /// <returns></returns>
        protected T GetProperty<T>(__VSHPROPID propId, uint itemid)
        {
            object value = null;
            int hr = hierarchy.GetProperty(itemid, (int)propId, out value);
            if( hr != VSConstants.S_OK || value == null )
            {
                return default(T);
            }
            return (T)value;
        }
        
        /// <summary>
        /// Gets the property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propId">The prop id.</param>
        /// <returns></returns>
        public T GetProperty<T>(__VSHPROPID propId)
        {
            return GetProperty<T>(propId, this.itemId);
        }

        /// <summary>
        /// Gets the parent.
        /// </summary>
        /// <value>The parent.</value>
        public HierarchyNode Parent
        {
            get
            {
                if( !IsRoot )
                {
                    return new HierarchyNode(solution, hierarchy);
                }
                else
                {
                    IVsHierarchy vsHierarchy = GetProperty<IVsHierarchy>(__VSHPROPID.VSHPROPID_ParentHierarchy, VSConstants.VSITEMID_ROOT);
                    if( vsHierarchy == null )
                    {
                        return null;
                    }

                    return new HierarchyNode(solution, vsHierarchy);
                }
            }
        }
    }

    #region NodeCollection
    internal sealed class HierarchyNodeEnumerator : IEnumerator<HierarchyNode>
    {
        HierarchyNode parent;
        uint currentItemId;

        public HierarchyNodeEnumerator(HierarchyNode parent)
        {
            Debug.Assert(parent != null);
            this.parent = (HierarchyNode)parent;
            this.Reset();
        }

        #region IEnumerator<HierarchyNode> Members

        public HierarchyNode Current
        {
            get
            {
                if( currentItemId != VSConstants.VSITEMID_NIL )
                {
                    return new HierarchyNode(parent, currentItemId);
                }
                return null;
            }
        }

        #endregion

        #region IDisposable Members

        private bool disposed;

        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // Take yourself off the Finalization queue 
            // to prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if( !this.disposed )
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if( disposing )
                {
                    // Dispose managed resources.
                }
                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.

                // Note that this is not thread safe.
                // Another thread could start disposing the object
                // after the managed resources are disposed,
                // but before the disposed flag is set to true.
                // If thread safety is necessary, it must be
                // implemented by the client.

            }
            disposed = true;
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method 
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~HierarchyNodeEnumerator()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get { return this.Current; }
        }

        public bool MoveNext()
        {
            if( this.currentItemId == VSConstants.VSITEMID_NIL )
            {
                this.currentItemId = parent.FirstChildId;
            }
            else
            {
                this.currentItemId = this.parent.GetNextChildId(currentItemId);
            }
            return ( this.currentItemId != VSConstants.VSITEMID_NIL );
        }

        public void Reset()
        {
            this.currentItemId = VSConstants.VSITEMID_NIL;
        }

        #endregion
    }

    internal class HierarchyNodeCollection : IEnumerable<HierarchyNode>, IDisposable
    {
        HierarchyNode parent;

        public HierarchyNodeCollection(HierarchyNode parent)
        {
            Debug.Assert(parent != null);
            this.parent = parent;
        }

        #region IDisposable Members

        private bool disposed;

        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // Take yourself off the Finalization queue 
            // to prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if( !this.disposed )
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if( disposing )
                {
                    // Dispose managed resources.
                }
                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.

                // Note that this is not thread safe.
                // Another thread could start disposing the object
                // after the managed resources are disposed,
                // but before the disposed flag is set to true.
                // If thread safety is necessary, it must be
                // implemented by the client.

            }
            disposed = true;
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method 
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~HierarchyNodeCollection()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        #endregion

        #region IEnumerable<HierarchyNode> Members

        /// <summary>
        /// Gets the Hierarchy enumerator object
        /// </summary>
        /// <returns></returns>
        public IEnumerator<HierarchyNode> GetEnumerator()
        {
            return new HierarchyNodeEnumerator(this.parent);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
        #endregion
}
